//------------------------------------------------------------------------------
// <copyright file="WinsockListeningSocket.cpp" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
#include "stdafx.h"

#ifndef _WINRT_DLL

#include "WinsockListeningSocket.h"
#include "WinsockStringSocket.h"
#include <ppltasks.h>
#include <ppl.h>
#include <Mstcpip.h>
#include <ws2tcpip.h>

using namespace Microsoft::Internal::GamesTest::Rpc::Server;
using namespace std;
using namespace concurrency;

#define HRESULT_FROM_WSA HRESULT_FROM_WIN32(WSAGetLastError())

WinsockListeningSocket::WinsockListeningSocket() : 
	m_isListening(false),
	m_listenSocket(INVALID_SOCKET),
	m_isSocketsInitialized(false),
	m_listeningLoopThread(nullptr)
{
}

WinsockListeningSocket::~WinsockListeningSocket(void)
{	
#pragma warning(suppress: 6031) // Ignoring return value is fine in destructor.
	Stop();
}

bool WinsockListeningSocket::GetIsListening() const
{
	return m_isListening;
}

GAMESTEST_ANNOTATE_HRESULT_METHOD HRESULT WinsockListeningSocket::StartImpl(int port)
{
	HRESULT hr = S_OK;

	CHK(CreateListeningSocket(port, m_listenSocket));

	if(listen(m_listenSocket, SOMAXCONN) == SOCKET_ERROR)
	{
		RpcPrintMessage(L"Failed to set listening socket to listen.");		
		hr = HRESULT_FROM_WSA;
		goto end;
	}	

	m_isListening = true;

	DWORD threadId;
	m_listeningLoopThread = CreateThread(
		nullptr,
		0,
		(LPTHREAD_START_ROUTINE)ThreadStart,
		this,
		0,
		&threadId);

	if(m_listeningLoopThread == NULL)
	{
		m_isListening = false; 
		RpcPrintMessage(L"WinsockListeningSocket create thread failed");			
		hr = HRESULT_FROM_WIN32(GetLastError());
		goto end;
	}

end:
	return hr;
}

GAMESTEST_ANNOTATE_HRESULT_METHOD HRESULT WinsockListeningSocket::StopImpl()
{
	HRESULT hr = S_OK;

	m_isListening = false;
		
	// Closing the socket forces the blocking calls like accept to unblock
	CloseListeningSocket();
	
	if(m_listeningLoopThread != nullptr)
	{
		WaitForSingleObject(m_listeningLoopThread, INFINITE);
		CloseHandle(m_listeningLoopThread);
	}

	return hr;
}

DWORD WinsockListeningSocket::Process()
{
	HRESULT hr = S_OK;	

	while(m_isListening)
	{
		SOCKET clientSocket = INVALID_SOCKET;

		clientSocket = accept(m_listenSocket, nullptr, nullptr);

		if(clientSocket == INVALID_SOCKET)
		{			
			int error;
			switch(error = WSAGetLastError())
			{									
			case WSAECONNRESET: // An incoming connection was indicated, but was subsequently terminated by the remote peer prior to accepting the call.
			case WSAENOBUFS:	// No buffer space is available.
				continue;				

			case WSAEINTR:		// This occurs when closesocket gets called in Shutdown and accept unblocks
				RpcPrintMessage(L"Accept was cancelled");		
				m_isListening = false;
				break;

			default:
				RpcPrintMessage(L"Accept failed");						
				hr = HRESULT_FROM_WIN32(error);
				m_isListening = false;
			}
		}
		else
		{					
			// From http://msdn.microsoft.com/en-us/library/windows/desktop/dd877220(v=vs.85).aspx
			// The keepalivetime member specifies the timeout, in milliseconds, with no activity until the first keep-alive packet is sent. 
			// The keepaliveinterval member specifies the interval, in milliseconds, between when successive keep-alive packets 
			// are sent if no acknowledgement is received.
			tcp_keepalive tcpKeepAlive;
			tcpKeepAlive.onoff = 1;
			tcpKeepAlive.keepalivetime = 1000 * 60 * 10; // 10 minutes
			tcpKeepAlive.keepaliveinterval = 1000; // 1 second

			// Bytes received, not used for the SIO_KEEPALIVE_VALS but still needed.
			DWORD unused;

			// Configure the keep alive times. Default times are 2 hrs and 1 second interval.
			int iResult = WSAIoctl(clientSocket, SIO_KEEPALIVE_VALS, &tcpKeepAlive, sizeof(tcp_keepalive), NULL, 0, &unused, NULL, NULL);
			if(iResult != 0)
			{
				RpcPrintMessage(L"Failed to set KeepAlive setting");
				hr = HRESULT_FROM_WSA;
				break;
			}

			IRpcStringSocket* stringSocket = new WinsockStringSocket(clientSocket);
			this->OnSocketConnected(stringSocket);
		}
	}

	CloseListeningSocket();		

	return hr;
}

HRESULT WinsockListeningSocket::CreateListeningSocket(int port, SOCKET & socket)
{
	HRESULT hr = S_OK;
	struct addrinfo * addr = nullptr;
	struct addrinfo hints;
	WSADATA wsaData;

	// Make sure port is valid before connecting
	if (port < 1 || port > 65535)
	{
		return E_INVALIDARG;
	}

	if(!m_isSocketsInitialized)
	{
		// Initialize Winsock
		int result = WSAStartup(MAKEWORD(2,2), &wsaData);
		if (result != 0) 
		{
			RpcPrintMessage(L"WSAStartup failed with error");
			hr = HRESULT_FROM_WIN32(result);
			goto end;
		}
	}

	m_isSocketsInitialized = true;

	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;

	// Resolve the server address and port
	char portToStringConversionBuffer[32] = {0};
	ZeroMemory(portToStringConversionBuffer, sizeof(portToStringConversionBuffer));
	_itoa_s<32>(port, portToStringConversionBuffer, 10);

	if (getaddrinfo(NULL, portToStringConversionBuffer, &hints, &addr) != 0 ) 
	{
		RpcPrintMessage(L"getaddrinfo failed with error");
		hr = HRESULT_FROM_WSA;
		goto end;
	}

	socket = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
	if (socket == INVALID_SOCKET) 
	{
		RpcPrintMessage(L"socket failed with error");
		hr = HRESULT_FROM_WSA;
		goto end;
	}

	if (::bind(socket, addr->ai_addr, (int)addr->ai_addrlen) == SOCKET_ERROR) 
	{
		RpcPrintMessage(L"bind failed with error");		
		hr = HRESULT_FROM_WSA;
		goto end;
	}

	// From http://msdn.microsoft.com/en-us/library/windows/desktop/dd877220(v=vs.85).aspx
	// The keepalivetime member specifies the timeout, in milliseconds, with no activity until the first keep-alive packet is sent. 
	// The keepaliveinterval member specifies the interval, in milliseconds, between when successive keep-alive packets 
	// are sent if no acknowledgement is received.
	tcp_keepalive tcpKeepAlive;
	tcpKeepAlive.onoff = 1;
	tcpKeepAlive.keepalivetime = 1000 * 60 * 10; // 10 minutes
	tcpKeepAlive.keepaliveinterval = 1000; // 1 second

	// Bytes received, not used for the SIO_KEEPALIVE_VALS but still needed.
	DWORD unused;

	// Configure the keep alive times. Default times are 2 hrs and 1 second interval.
	if(WSAIoctl(socket, SIO_KEEPALIVE_VALS, &tcpKeepAlive, sizeof(tcp_keepalive), NULL, 0, &unused, NULL, NULL) != 0)
	{
		RpcPrintMessage(L"Failed to set KeepAlive setting");
		hr = HRESULT_FROM_WSA;
		goto end;
	}

end:
	if(FAILED(hr))
	{
		RpcPrintError(hr);				
		if(socket != INVALID_SOCKET)
		{
			if(closesocket(socket) == SOCKET_ERROR)
			{
				// We've already failed and want to return the original error
				RpcPrintError(HRESULT_FROM_WSA);
			}

			socket = INVALID_SOCKET;
		}
	}

	if(addr != nullptr)
	{
		freeaddrinfo(addr);	
	}

	return hr;
}

void WinsockListeningSocket::CloseListeningSocket()
{	
	concurrency::critical_section::scoped_lock lock(s_synchronizationContext);

	if(m_listenSocket != INVALID_SOCKET)
	{
		closesocket(m_listenSocket);
		m_listenSocket = INVALID_SOCKET;
	}

	if(m_isSocketsInitialized)
	{		
		WSACleanup();
		m_isSocketsInitialized = false;
	}
}

DWORD WinsockListeningSocket::ThreadStart(LPVOID instance)
{
	return ((WinsockListeningSocket*)instance)->Process();
}

#endif // _WINRT_DLL